Skip to content

Manage resource types via OCI (create/get/list/publish/pull/delete/convert) - #249

Open
chrisghill wants to merge 8 commits into
mainfrom
resource-type-oci
Open

Manage resource types via OCI (create/get/list/publish/pull/delete/convert)#249
chrisghill wants to merge 8 commits into
mainfrom
resource-type-oci

Conversation

@chrisghill

Copy link
Copy Markdown
Member

The API now supports resource types being managed via OCI push/pull. This wires that up in the CLI, mirroring bundles but without any build/lint machinery, extracts the shared OCI plumbing into its own package so bundles and resource types share one code path, and moves resource-type reads onto the SDK so the local internal/api holding-pen package can be deleted entirely.

Shared OCI package

  • New internal/oci package holds the raw OCI logic: Publisher (Package with a caller-supplied artifact-type + per-file keep predicate, Publish) and Puller (Pull), plus the file/mime helpers.
  • internal/bundle keeps only its unique config (ArtifactType const + PackageKeep ignore-list predicate); internal/commands/bundle/{publish,pull} and internal/commands/instance/export.go now drive oci.Publisher/oci.Puller.
  • Bundle behavior is unchanged — same media type (application/vnd.massdriver.bundle.v1+json), same .mdignore keep semantics, same manifest/layers, same pull tag resolution.

Resource types → OCI (7 commands)

Command Behavior
create Creates a resource-type OCI repository; supports -a attributes
get Uses the SDK's ResourceTypes.Get (returns the resolved JSON schema); -o json prints the full object, new --schema prints only the schema (errors without -o json)
list Lists from the OCI-repo catalog filtered to resource-type artifacts, mirroring bundle list (columns: Name / Latest / Created At; supports -o json)
publish [path] Bundle-style directory (default .); requires version in massdriver.yaml; meta-schema validation; local duplicate-version check (stable only, no dev releases); packages only allowlisted files; rejects raw JSON schema files, pointing to convert
pull OCI pull (-d/-v/-f), latest resolves to the latest release channel or newest tag
delete Deletes the OCI repository, failing locally if it has published versions (immutable); keeps the type-the-name confirmation
convert Converts a raw JSON/YAML schema into a massdriver.yaml, extracting inlined instruction/export content back out to referenced files
  • Added a version field to the resource type massdriver.yaml.
  • Publish allowlist: massdriver.yaml, readme, changelog, icons, plus the instructions/ and exports/ directories a massdriver.yaml references (so convert → publish → pull round-trips). Everything else is silently skipped.
  • Raw JSON schema publishing is no longer supported.

Removed internal/api

  • Resource-type reads (Get, List) now go through the SDK (ResourceTypes.Get and OciRepos.Iter), so the temporary internal/api holding-pen package is deleted in full — no callers remain.
  • resourcetype.ResourceType is now an alias of the SDK's resourcetypes.ResourceType.

Tests & docs

  • New/updated tests: bundle packaging against the shared packager, OCI pull, publish validation (raw-schema rejection, missing name/version), packageKeep allowlist, convert round-trip (incl. duplicate-label collision and no-clobber), and resource-type get/list mocked via massdriver.WithGQLClient.
  • Added helpdocs for create/pull/convert, refreshed publish, and regenerated the committed CLI docs.

Notes

  • list table columns changed from ID / Name / Updated At to Name / Latest / Created At to match bundle list.
  • Requires the SDK bump to v0.2.18 (adds the ResourceTypes service).

@chrisghill

Copy link
Copy Markdown
Member Author

@claude review

@claude

This comment was marked as outdated.

@chrisghill

Copy link
Copy Markdown
Member Author

@claude review

@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown

Claude finished @chrisghill's task in 2m 19s —— View job


Claude's Review

Todo list

  • Gather context (diff, changed files, repo guidelines)
  • Review shared internal/oci package
  • Review internal/commands/resourcetype/* (create/get/list/publish/pull/delete/convert)
  • Review internal/bundle changes (shared packager wiring)
  • Review internal/resourcetype/{build,get}.go (SDK migration)
  • Post final review

Overall this is a clean refactor — the internal/oci extraction is a nice reduction in duplication, bundle behavior is preserved (publish/pull now just route through oci.Publisher/oci.Puller with the same media type and .mdignore semantics), and the resource-type command surface is well tested (keep_test.go, convert_test.go, publish_test.go cover the tricky edge cases like path traversal and filename-collision on convert). Nice attention to detail — e.g. TestRunConvertDistinctFilesForDuplicateLabels specifically encodes the old buggy behavior it fixes.

A few things worth a look:

1. Stale golangci-lint exclusion for the deleted internal/api package
.golangci.yaml:419-422 still has a revive/var-naming exclusion scoped to path: "internal/api/", but that package is deleted in this PR. Since exclusions.warn-unused: true is set (.golangci.yaml:408), lint will now flag this rule as unused. Worth deleting that rule block in this PR since it's directly caused by this change.
Fix this →

2. packageKeep case-sensitivity asymmetry (internal/commands/resourcetype/publish.go:53-64)
Top-level allowlisted files (readme.md, icon.png, etc.) are matched case-insensitively via strings.ToLower, but referenced instruction/export paths are matched with an exact-case lookup in the referenced map. This is likely intentional (references should match what's actually in massdriver.yaml), but it means a massdriver.yaml referencing ./Instructions/CLI.md on a case-sensitive filesystem would silently drop the file from the package (caught by validateReferencedFiles only if the exact-case file doesn't exist — but if the file happens to exist with different case, it'd still pass validation yet get skipped by packageKeep, since validateReferencedFiles stats filepath.Join(srcDir, norm) which is case-sensitive on Linux too... actually these two functions use the same normalizeRel/casing, so they should agree in practice). Minor, not blocking — just flagging as something to keep in mind since the two allowlist paths behave differently.

3. resourcetype.Build doesn't validate referenced-file paths itself (internal/resourcetype/build.go:94-124)
Build reads instruction/export files via filepath.Join(baseDir, instruction.Path) with no path-traversal guard. This is safe today because RunPublish always calls validateReferencedFiles (which rejects absolute/escaping paths) before validateSchemaReadBuild (internal/commands/resourcetype/publish.go:130-142). But Build/Read are exported and could be called directly by future callers without that guard. Not a bug in this PR, just worth a comment noting the invariant so it doesn't get silently violated later.

4. Minor: symlink escapes not covered
validateReferencedFiles/normalizeRel guard against ..-escaping and absolute paths, but not symlinks inside the resource-type directory that point outside it. This mirrors the pre-existing bundle .mdignore trust model (not a regression), so not blocking — just noting it's the same trust boundary as before.

Nothing here blocks merging — items 1 is a quick cleanup, 2–4 are minor/defense-in-depth notes rather than bugs found in the new code paths. The SDK migration (internal/resourcetype/get.go), OCI pull/publish flows, and convert round-trip logic all look correct and are well covered by tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant